package k8s_agent

import (
	devcontainer_api_v1 "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/api/v1"
	devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto"
	devcontainer_errors "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/errors"
	devcontainer_module_utils "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/utils"
	devcontainer_agent_module_vo "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/vo"
	"code.gitea.io/gitea/modules/setting"
	"context"
	"fmt"
	apimachinery_api_metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	apimachinery_apis_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	apimachinery_apis_v1_unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	apimachinery_runtime_utils "k8s.io/apimachinery/pkg/runtime"
	apimachinery_watch "k8s.io/apimachinery/pkg/watch"
	dynamic_client "k8s.io/client-go/dynamic"
)

func GetDevcontainer(ctx *context.Context, client dynamic_client.Interface, opts *devcontainer_dto.GetDevcontainerOptions) (*devcontainer_api_v1.DevcontainerApp, error) {

	// 0. 检查参数
	if ctx == nil || opts == nil || len(opts.Namespace) == 0 || len(opts.Name) == 0 {
		return nil, devcontainer_errors.ErrIllegalDevcontainerParameters{
			FieldList: []string{"ctx", "opts", "opts.Name", "opts.Namespace"},
			Message:   "cannot be nil",
		}
	}

	// 1. 获取 k8s CRD 资源 DevcontainerApp
	devcontainerUnstructured, err := client.Resource(groupVersionResource).Namespace(opts.Namespace).Get(*ctx, opts.Name, opts.GetOptions)
	if err != nil {
		return nil, devcontainer_errors.ErrOperateDevcontainer{
			Action:  "Get DevcontainerApp thru k8s API Server",
			Message: err.Error(),
		}
	}

	// 2. 解析 DevcontainerApp Status 域，装填 VO
	devcontainerApp := &devcontainer_api_v1.DevcontainerApp{}
	err = apimachinery_runtime_utils.DefaultUnstructuredConverter.FromUnstructured(devcontainerUnstructured.Object, &devcontainerApp)
	if err != nil {
		return nil, devcontainer_errors.ErrOperateDevcontainer{
			Action:  "Convert k8s API Server unstructured response into DevcontainerApp",
			Message: err.Error(),
		}
	}

	// 3. 检查 Devcontainer 是否就绪
	if !devcontainer_module_utils.IsK8sDevcontainerStatusReady(&devcontainerApp.Status) {
		// 3.1 检查 Wait 参数，若用户不需要阻塞式等待，直接返回 “DevContainer 未就绪” 错误
		if opts.Wait == false {
			return nil, devcontainer_errors.ErrK8sDevcontainerNotReady{
				Name:      opts.Name,
				Namespace: opts.Namespace,
				Wait:      opts.Wait,
			}
		}

		// 3.2 执行阻塞式等待
		devcontainerStatusVO, err := waitUntilDevcontainerReadyWithTimeout(ctx, client, opts)
		if err != nil {
			return nil, devcontainer_errors.ErrOperateDevcontainer{
				Action:  "wait for k8s DevContainer to be ready",
				Message: err.Error(),
			}
		}
		devcontainerApp.Status.Ready = devcontainerStatusVO.Ready
		devcontainerApp.Status.NodePortAssigned = devcontainerStatusVO.NodePortAssigned
	}

	// 4. 将就绪的 DevContainer Status VO 返回
	return devcontainerApp, nil
}

// waitUntilDevcontainerReadyWithTimeout 辅助方法：在超时时间内阻塞等待 DevContainer 就绪
func waitUntilDevcontainerReadyWithTimeout(ctx *context.Context, client dynamic_client.Interface, opts *devcontainer_dto.GetDevcontainerOptions) (*devcontainer_agent_module_vo.DevcontainerStatusK8sAgentVO, error) {

	// 0. 检查参数
	if ctx == nil || client == nil || opts == nil || len(opts.Name) == 0 || len(opts.Namespace) == 0 {
		return nil, devcontainer_errors.ErrIllegalDevcontainerParameters{
			FieldList: []string{"ctx", "client", "opts", "opts.Name", "opts.Namespace"},
			Message:   "could not be nil",
		}
	}

	// 1. 注册 watcher 监听 DevContainer Status 变化
	watcherTimeoutSeconds := setting.Devstar.Devcontainer.TimeoutSeconds
	watcher, err := client.Resource(groupVersionResource).Namespace(opts.Namespace).Watch(*ctx, apimachinery_apis_v1.ListOptions{
		FieldSelector:  fmt.Sprintf("metadata.name=%s", opts.Name),
		Watch:          true,
		TimeoutSeconds: &watcherTimeoutSeconds,
	})
	if err != nil {
		return nil, devcontainer_errors.ErrOperateDevcontainer{
			Action:  "register watcher of DevContainer Readiness",
			Message: err.Error(),
		}
	}
	defer watcher.Stop()

	// 2. 当 DevContainer Watcher 事件处理
	devcontainerStatusVO := &devcontainer_agent_module_vo.DevcontainerStatusK8sAgentVO{}
	for event := range watcher.ResultChan() {
		switch event.Type {
		case apimachinery_watch.Added:
			// 2.1 监听 DevcontainerApp ADDED 事件，直接 fallthrough 到 MODIFIED 事件合并处理
			fallthrough
		case apimachinery_watch.Modified:
			// 2.2 监听 DevcontainerApp MODIFIED 事件
			if devcontainerUnstructured, ok := event.Object.(*apimachinery_apis_v1_unstructured.Unstructured); ok {
				// 2.2.1 解析 status 域
				statusDevcontainer, ok, err := apimachinery_apis_v1_unstructured.NestedMap(devcontainerUnstructured.Object, "status")
				if err == nil && ok {
					devcontainerCurrentStatus := &devcontainer_api_v1.DevcontainerAppStatus{
						Ready:            statusDevcontainer["ready"].(bool),
						NodePortAssigned: uint16(statusDevcontainer["nodePortAssigned"].(int64)),
					}
					// 2.2.2 当 Status 达到就绪状态后，返回
					if devcontainer_module_utils.IsK8sDevcontainerStatusReady(devcontainerCurrentStatus) {
						devcontainerStatusVO.Ready = devcontainerCurrentStatus.Ready
						devcontainerStatusVO.NodePortAssigned = devcontainerCurrentStatus.NodePortAssigned
						return devcontainerStatusVO, nil
					}
				}
			}
		case apimachinery_watch.Error:
			// 2.3 监听 DevcontainerApp ERROR 事件，返回报错信息
			apimachineryApiMetav1Status, ok := event.Object.(*apimachinery_api_metav1.Status)
			if !ok {
				return nil, devcontainer_errors.ErrOperateDevcontainer{
					Action: fmt.Sprintf("wait for Devcontainer '%s' in namespace '%s' to be ready", opts.Name, opts.Namespace),
					Message: fmt.Sprintf("An error occurred in k8s CRD DevcontainerApp Watcher: \n"+
						"   Code: %v (status = %v)\n"+
						"Message: %v\n"+
						" Reason: %v\n"+
						"Details: %v",
						apimachineryApiMetav1Status.Code, apimachineryApiMetav1Status.Status,
						apimachineryApiMetav1Status.Message,
						apimachineryApiMetav1Status.Reason,
						apimachineryApiMetav1Status.Details),
				}
			}
		case apimachinery_watch.Deleted:
			// 2.4 监听 DevcontainerApp DELETED 事件，返回报错信息
			return nil, devcontainer_errors.ErrOperateDevcontainer{
				Action:  fmt.Sprintf("Open DevContainer '%s' in namespace '%s'", opts.Name, opts.Namespace),
				Message: fmt.Sprintf("'%s' of Kind DevcontainerApp has been Deleted", opts.Name),
			}
		}
	}

	// 3. k8s CRD DevcontainerApp Watcher 超时关闭处理：直接返回超时错误
	return nil, devcontainer_errors.ErrOpenDevcontainerTimeout{
		Name:           opts.Name,
		Namespace:      opts.Namespace,
		TimeoutSeconds: setting.Devstar.Devcontainer.TimeoutSeconds,
	}
}
